import { Cartesian3 } from '../../Source/Cesium.js';
import { Cartesian4 } from '../../Source/Cesium.js';
import { Math as CesiumMath } from '../../Source/Cesium.js';
import { Matrix3 } from '../../Source/Cesium.js';
import { Matrix4 } from '../../Source/Cesium.js';
import { Quaternion } from '../../Source/Cesium.js';
import { TranslationRotationScale } from '../../Source/Cesium.js';

describe('Core/Matrix4', function() {

    it('default constructor creates values array with all zeros.', function() {
        var matrix = new Matrix4();
        expect(matrix[Matrix4.COLUMN0ROW0]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN1ROW0]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN2ROW0]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN3ROW0]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN0ROW1]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN1ROW1]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN2ROW1]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN3ROW1]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN0ROW2]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN1ROW2]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN2ROW2]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN3ROW2]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN0ROW3]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN1ROW3]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN2ROW3]).toEqual(0.0);
        expect(matrix[Matrix4.COLUMN3ROW3]).toEqual(0.0);
    });

    it('constructor sets properties from parameters.', function() {
        var matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(matrix[Matrix4.COLUMN0ROW0]).toEqual(1.0);
        expect(matrix[Matrix4.COLUMN1ROW0]).toEqual(2.0);
        expect(matrix[Matrix4.COLUMN2ROW0]).toEqual(3.0);
        expect(matrix[Matrix4.COLUMN3ROW0]).toEqual(4.0);
        expect(matrix[Matrix4.COLUMN0ROW1]).toEqual(5.0);
        expect(matrix[Matrix4.COLUMN1ROW1]).toEqual(6.0);
        expect(matrix[Matrix4.COLUMN2ROW1]).toEqual(7.0);
        expect(matrix[Matrix4.COLUMN3ROW1]).toEqual(8.0);
        expect(matrix[Matrix4.COLUMN0ROW2]).toEqual(9.0);
        expect(matrix[Matrix4.COLUMN1ROW2]).toEqual(10.0);
        expect(matrix[Matrix4.COLUMN2ROW2]).toEqual(11.0);
        expect(matrix[Matrix4.COLUMN3ROW2]).toEqual(12.0);
        expect(matrix[Matrix4.COLUMN0ROW3]).toEqual(13.0);
        expect(matrix[Matrix4.COLUMN1ROW3]).toEqual(14.0);
        expect(matrix[Matrix4.COLUMN2ROW3]).toEqual(15.0);
        expect(matrix[Matrix4.COLUMN3ROW3]).toEqual(16.0);
    });

    it('can pack and unpack', function() {
        var array = [];
        var matrix4 = new Matrix4(
                1.0, 2.0, 3.0, 4.0,
                5.0, 6.0, 7.0, 8.0,
                9.0, 10.0, 11.0, 12.0,
                13.0, 14.0, 15.0, 16.0);
        Matrix4.pack(matrix4, array);
        expect(array.length).toEqual(Matrix4.packedLength);
        expect(Matrix4.unpack(array)).toEqual(matrix4);
    });

    it('can pack and unpack with offset', function() {
        var packed = new Array(3);
        var offset = 3;
        var matrix4 = new Matrix4(
                1.0, 2.0, 3.0, 4.0,
                5.0, 6.0, 7.0, 8.0,
                9.0, 10.0, 11.0, 12.0,
                13.0, 14.0, 15.0, 16.0);

        Matrix4.pack(matrix4, packed, offset);
        expect(packed.length).toEqual(offset + Matrix4.packedLength);

        var result = new Matrix4();
        var returnedResult = Matrix4.unpack(packed, offset, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(matrix4);
    });

    it('pack throws with undefined matrix4', function() {
        var array = [];
        expect(function() {
            Matrix4.pack(undefined, array);
        }).toThrowDeveloperError();
    });

    it('pack throws with undefined array', function() {
        var matrix4 = new Matrix4();
        expect(function() {
            Matrix4.pack(matrix4, undefined);
        }).toThrowDeveloperError();
    });

    it('unpack throws with undefined array', function() {
        expect(function() {
            Matrix4.unpack(undefined);
        }).toThrowDeveloperError();
    });

    it('fromArray works without a result parameter', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var matrix = Matrix4.fromArray([1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0, 4.0, 8.0, 12.0, 16.0]);
        expect(matrix).toEqual(expected);
    });

    it('fromArray works with a result parameter', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var result = new Matrix4();
        var matrix = Matrix4.fromArray([1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0, 4.0, 8.0, 12.0, 16.0], 0, result);
        expect(matrix).toBe(result);
        expect(matrix).toEqual(expected);
    });

    it('fromArray works with a starting index', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var result = new Matrix4();
        var matrix = Matrix4.fromArray([0.0, 0.0, 0.0, 1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0, 4.0, 8.0, 12.0, 16.0], 3, result);
        expect(matrix).toBe(result);
        expect(matrix).toEqual(expected);
    });

    it('fromRowMajorArray works without a result parameter', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var matrix = Matrix4.fromRowMajorArray([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0]);
        expect(matrix).toEqual(expected);
    });

    it('fromRowMajorArray works with a result parameter', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var result = new Matrix4();
        var matrix = Matrix4.fromRowMajorArray([1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0], result);
        expect(matrix).toBe(result);
        expect(matrix).toEqual(expected);
    });

    it('fromColumnMajorArray works without a result parameter', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var matrix = Matrix4.fromColumnMajorArray([1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0, 4.0, 8.0, 12.0, 16.0]);
        expect(matrix).toEqual(expected);
    });

    it('fromColumnMajorArray works with a result parameter', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var result = new Matrix4();
        var matrix = Matrix4.fromColumnMajorArray([1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0, 4.0, 8.0, 12.0, 16.0], result);
        expect(matrix).toBe(result);
        expect(matrix).toEqual(expected);
    });

    it('clone works without a result parameter', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var returnedResult = expected.clone();
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqual(expected);
    });

    it('clone works with a result parameter', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var result = new Matrix4();
        var returnedResult = expected.clone(result);
        expect(returnedResult).toBe(result);
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqual(expected);
    });

    it('fromRotationTranslation works without a result parameter', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 10.0, 4.0, 5.0, 6.0, 11.0, 7.0, 8.0, 9.0, 12.0, 0.0, 0.0, 0.0, 1.0);
        var returnedResult = Matrix4.fromRotationTranslation(new Matrix3(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0), new Cartesian3(10.0, 11.0, 12.0));
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqual(expected);
    });

    it('fromRotationTranslation works with a result parameter', function() {
        var expected = new Matrix4(1.0, 2.0, 3.0, 10.0, 4.0, 5.0, 6.0, 11.0, 7.0, 8.0, 9.0, 12.0, 0.0, 0.0, 0.0, 1.0);
        var result = new Matrix4();
        var returnedResult = Matrix4.fromRotationTranslation(new Matrix3(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0), new Cartesian3(10.0, 11.0, 12.0), result);
        expect(returnedResult).toBe(result);
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqual(expected);
    });

    it('fromTranslation works without a result parameter', function() {
        var expected = new Matrix4(1.0, 0.0, 0.0, 10.0, 0.0, 1.0, 0.0, 11.0, 0.0, 0.0, 1.0, 12.0, 0.0, 0.0, 0.0, 1.0);
        var returnedResult = Matrix4.fromTranslation(new Cartesian3(10.0, 11.0, 12.0));
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqual(expected);
    });

    it('fromTranslationQuaternionRotationScale works without a result parameter', function() {
        var expected = new Matrix4(
            7.0,  0.0, 0.0, 1.0,
            0.0,  0.0, 9.0, 2.0,
            0.0, -8.0, 0.0, 3.0,
            0.0,  0.0, 0.0, 1.0);
        var returnedResult = Matrix4.fromTranslationQuaternionRotationScale(
            new Cartesian3(1.0, 2.0, 3.0),                                            // translation
            Quaternion.fromAxisAngle(Cartesian3.UNIT_X, CesiumMath.toRadians(-90.0)), // rotation
            new Cartesian3(7.0, 8.0, 9.0));                                           // scale
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqualEpsilon(expected, CesiumMath.EPSILON14);
    });

    it('fromTranslationQuaternionRotationScale works with a result parameter', function() {
        var expected = new Matrix4(
                7.0,  0.0, 0.0, 1.0,
                0.0,  0.0, 9.0, 2.0,
                0.0, -8.0, 0.0, 3.0,
                0.0,  0.0, 0.0, 1.0);
        var result = new Matrix4();
        var returnedResult = Matrix4.fromTranslationQuaternionRotationScale(
            new Cartesian3(1.0, 2.0, 3.0),                                            // translation
            Quaternion.fromAxisAngle(Cartesian3.UNIT_X, CesiumMath.toRadians(-90.0)), // rotation
            new Cartesian3(7.0, 8.0, 9.0),                                            // scale
            result);
        expect(returnedResult).toBe(result);
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqualEpsilon(expected, CesiumMath.EPSILON14);
    });

    it('fromTranslationRotationScale works without a result parameter', function() {
        var expected = new Matrix4(
            7.0,  0.0, 0.0, 1.0,
            0.0,  0.0, 9.0, 2.0,
            0.0, -8.0, 0.0, 3.0,
            0.0,  0.0, 0.0, 1.0);

        var trs = new TranslationRotationScale(new Cartesian3(1.0, 2.0, 3.0),
            Quaternion.fromAxisAngle(Cartesian3.UNIT_X, CesiumMath.toRadians(-90.0)),
            new Cartesian3(7.0, 8.0, 9.0));

        var returnedResult = Matrix4.fromTranslationRotationScale(trs);
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqualEpsilon(expected, CesiumMath.EPSILON14);
    });

    it('fromTranslationRotationScale works with a result parameter', function() {
        var expected = new Matrix4(
            7.0,  0.0, 0.0, 1.0,
            0.0,  0.0, 9.0, 2.0,
            0.0, -8.0, 0.0, 3.0,
            0.0,  0.0, 0.0, 1.0);

        var trs = new TranslationRotationScale(new Cartesian3(1.0, 2.0, 3.0),
            Quaternion.fromAxisAngle(Cartesian3.UNIT_X, CesiumMath.toRadians(-90.0)),
            new Cartesian3(7.0, 8.0, 9.0));

        var result = new Matrix4();
        var returnedResult = Matrix4.fromTranslationRotationScale(trs, result);
        expect(returnedResult).toBe(result);
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqualEpsilon(expected, CesiumMath.EPSILON14);
    });

    it('fromTranslation works with a result parameter', function() {
        var expected = new Matrix4(1.0, 0.0, 0.0, 10.0, 0.0, 1.0, 0.0, 11.0, 0.0, 0.0, 1.0, 12.0, 0.0, 0.0, 0.0, 1.0);
        var result = new Matrix4();
        var returnedResult = Matrix4.fromTranslation(new Cartesian3(10.0, 11.0, 12.0), result);
        expect(returnedResult).toBe(result);
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqual(expected);
    });

    it('fromScale works without a result parameter', function() {
        var expected = new Matrix4(
                7.0, 0.0, 0.0, 0.0,
                0.0, 8.0, 0.0, 0.0,
                0.0, 0.0, 9.0, 0.0,
                0.0, 0.0, 0.0, 1.0);
        var returnedResult = Matrix4.fromScale(new Cartesian3(7.0, 8.0, 9.0));
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqual(expected);
    });

    it('fromScale works with a result parameter', function() {
        var expected = new Matrix4(
                7.0, 0.0, 0.0, 0.0,
                0.0, 8.0, 0.0, 0.0,
                0.0, 0.0, 9.0, 0.0,
                0.0, 0.0, 0.0, 1.0);
        var result = new Matrix4();
        var returnedResult = Matrix4.fromScale(new Cartesian3(7.0, 8.0, 9.0), result);
        expect(returnedResult).toBe(result);
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqual(expected);
    });

    it('fromUniformScale works without a result parameter', function() {
        var expected = new Matrix4(
                2.0, 0.0, 0.0, 0.0,
                0.0, 2.0, 0.0, 0.0,
                0.0, 0.0, 2.0, 0.0,
                0.0, 0.0, 0.0, 1.0);
        var returnedResult = Matrix4.fromUniformScale(2.0);
        expect(returnedResult).toEqual(expected);
    });

    it('fromUniformScale works with a result parameter', function() {
        var expected = new Matrix4(
                2.0, 0.0, 0.0, 0.0,
                0.0, 2.0, 0.0, 0.0,
                0.0, 0.0, 2.0, 0.0,
                0.0, 0.0, 0.0, 1.0);
        var result = new Matrix4();
        var returnedResult = Matrix4.fromUniformScale(2.0, result);
        expect(returnedResult).toBe(result);
        expect(returnedResult).toEqual(expected);
    });

    it('computePerspectiveFieldOfView works', function() {
        var expected = new Matrix4(1, 0,                   0,                  0,
                                   0, 1,                  0,                  0,
                                   0, 0, -1.222222222222222, -2.222222222222222,
                                   0, 0,                  -1,                 0);
        var result = new Matrix4();
        var returnedResult = Matrix4.computePerspectiveFieldOfView(CesiumMath.PI_OVER_TWO, 1, 1, 10, result);
        expect(returnedResult).toEqualEpsilon(expected, CesiumMath.EPSILON15);
    });

    it('fromCamera works without a result parameter', function() {
        var expected = Matrix4.IDENTITY;
        var returnedResult = Matrix4.fromCamera({
            position : Cartesian3.ZERO,
            direction : Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
            up : Cartesian3.UNIT_Y
        });
        expect(expected).toEqual(returnedResult);
    });

    it('fromCamera works with a result parameter', function() {
        var expected = Matrix4.IDENTITY;
        var result = new Matrix4();
        var returnedResult = Matrix4.fromCamera({
            position : Cartesian3.ZERO,
            direction : Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
            up : Cartesian3.UNIT_Y
        }, result);
        expect(returnedResult).toBe(result);
        expect(returnedResult).toEqual(expected);
    });

    it('computeOrthographicOffCenter works', function() {
        var expected = new Matrix4(2, 0, 0, -1, 0, 2, 0, -5, 0, 0, -2, -1, 0, 0, 0, 1);
        var result = new Matrix4();
        var returnedResult = Matrix4.computeOrthographicOffCenter(0, 1, 2, 3, 0, 1, result);
        expect(returnedResult).toBe(result);
        expect(returnedResult).toEqual(expected);
    });

    it('computeViewportTransformation  works', function() {
        var expected = new Matrix4(2.0, 0.0, 0.0, 2.0,
                                   0.0, 3.0, 0.0, 3.0,
                                   0.0, 0.0, 1.0, 1.0,
                                   0.0, 0.0, 0.0, 1.0);
        var result = new Matrix4();
        var returnedResult = Matrix4.computeViewportTransformation({
            x : 0,
            y : 0,
            width : 4.0,
            height : 6.0
        }, 0.0, 2.0, result);
        expect(returnedResult).toEqual(expected);
        expect(returnedResult).toBe(result);
    });

    it('computePerspectiveOffCenter works', function() {
        var expected = new Matrix4(2, 0, 3, 0, 0, 2, 5, 0, 0, 0, -3, -4, 0, 0, -1, 0);
        var result = new Matrix4();
        var returnedResult = Matrix4.computePerspectiveOffCenter(1, 2, 2, 3, 1, 2, result);
        expect(returnedResult).toEqual(expected);
        expect(returnedResult).toBe(result);
    });

    it('computeInfinitePerspectiveOffCenter  works', function() {
        var expected = new Matrix4(2, 0, 3, 0, 0, 2, 5, 0, 0, 0, -1, -2, 0, 0, -1, 0);
        var result = new Matrix4();
        var returnedResult = Matrix4.computeInfinitePerspectiveOffCenter(1, 2, 2, 3, 1, result);
        expect(returnedResult).toEqual(expected);
    });

    it('toArray works without a result parameter', function() {
        var expected = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0];
        var returnedResult = Matrix4.toArray(Matrix4.fromColumnMajorArray(expected));
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqual(expected);
    });

    it('toArray works with a result parameter', function() {
        var expected = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0];
        var result = [];
        var returnedResult = Matrix4.toArray(Matrix4.fromColumnMajorArray(expected), result);
        expect(returnedResult).toBe(result);
        expect(returnedResult).not.toBe(expected);
        expect(returnedResult).toEqual(expected);
    });

    it('getElementIndex works', function() {
        var i = 0;
        for ( var col = 0; col < 4; col++) {
            for ( var row = 0; row < 4; row++) {
                var index = Matrix4.getElementIndex(col, row);
                expect(index).toEqual(i);
                i++;
            }
        }
    });

    it('getColumn works for each column', function() {
        var matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var expectedColumn0 = new Cartesian4(1.0, 5.0, 9.0, 13.0);
        var expectedColumn1 = new Cartesian4(2.0, 6.0, 10.0, 14.0);
        var expectedColumn2 = new Cartesian4(3.0, 7.0, 11.0, 15.0);
        var expectedColumn3 = new Cartesian4(4.0, 8.0, 12.0, 16.0);

        var resultColumn0 = new Cartesian4();
        var resultColumn1 = new Cartesian4();
        var resultColumn2 = new Cartesian4();
        var resultColumn3 = new Cartesian4();
        var returnedResultColumn0 = Matrix4.getColumn(matrix, 0, resultColumn0);
        var returnedResultColumn1 = Matrix4.getColumn(matrix, 1, resultColumn1);
        var returnedResultColumn2 = Matrix4.getColumn(matrix, 2, resultColumn2);
        var returnedResultColumn3 = Matrix4.getColumn(matrix, 3, resultColumn3);

        expect(resultColumn0).toBe(returnedResultColumn0);
        expect(resultColumn0).toEqual(expectedColumn0);
        expect(resultColumn1).toBe(returnedResultColumn1);
        expect(resultColumn1).toEqual(expectedColumn1);
        expect(resultColumn2).toBe(returnedResultColumn2);
        expect(resultColumn2).toEqual(expectedColumn2);
        expect(resultColumn3).toBe(returnedResultColumn3);
        expect(resultColumn3).toEqual(expectedColumn3);
    });

    it('setColumn works for each column', function() {
        var matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);

        var result = new Matrix4();

        var expected = new Matrix4(17.0, 2.0, 3.0, 4.0, 18.0, 6.0, 7.0, 8.0, 19.0, 10.0, 11.0, 12.0, 20.0, 14.0, 15.0, 16.0);
        var returnedResult = Matrix4.setColumn(matrix, 0, new Cartesian4(17.0, 18.0, 19.0, 20.0), result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);

        expected = new Matrix4(1.0, 17.0, 3.0, 4.0, 5.0, 18.0, 7.0, 8.0, 9.0, 19.0, 11.0, 12.0, 13.0, 20.0, 15.0, 16.0);
        returnedResult = Matrix4.setColumn(matrix, 1, new Cartesian4(17.0, 18.0, 19.0, 20.0), result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);

        expected = new Matrix4(1.0, 2.0, 17.0, 4.0, 5.0, 6.0, 18.0, 8.0, 9.0, 10.0, 19.0, 12.0, 13.0, 14.0, 20.0, 16.0);
        returnedResult = Matrix4.setColumn(matrix, 2, new Cartesian4(17.0, 18.0, 19.0, 20.0), result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);

        expected = new Matrix4(1.0, 2.0, 3.0, 17.0, 5.0, 6.0, 7.0, 18.0, 9.0, 10.0, 11.0, 19.0, 13.0, 14.0, 15.0, 20.0);
        returnedResult = Matrix4.setColumn(matrix, 3, new Cartesian4(17.0, 18.0, 19.0, 20.0), result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);
    });

    it('setTranslation works', function() {
        var matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var translation = new Cartesian3(-1.0, -2.0, -3.0);
        var result = new Matrix4();

        var expected = new Matrix4(1.0, 2.0, 3.0, -1.0, 5.0, 6.0, 7.0, -2.0, 9.0, 10.0, 11.0, -3.0, 13.0, 14.0, 15.0, 16.0);
        var returnedResult = Matrix4.setTranslation(matrix, translation, result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);
    });

    it('setScale works', function() {
        var matrix = Matrix4.clone(Matrix4.IDENTITY);
        var result = new Matrix4();
        var newScale = new Cartesian3(1.0, 2.0, 3.0);

        expect(Matrix4.getScale(matrix, new Cartesian3())).toEqual(new Cartesian3(1.0, 1.0, 1.0));

        var returnedResult = Matrix4.setScale(matrix, newScale, result);

        expect(Matrix4.getScale(returnedResult, new Cartesian3())).toEqual(newScale);
        expect(result).toBe(returnedResult);
    });

    it('getRow works for each row', function() {
        var matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var expectedRow0 = new Cartesian4(1.0, 2.0, 3.0, 4.0);
        var expectedRow1 = new Cartesian4(5.0, 6.0, 7.0, 8.0);
        var expectedRow2 = new Cartesian4(9.0, 10.0, 11.0, 12.0);
        var expectedRow3 = new Cartesian4(13.0, 14.0, 15.0, 16.0);

        var resultRow0 = new Cartesian4();
        var resultRow1 = new Cartesian4();
        var resultRow2 = new Cartesian4();
        var resultRow3 = new Cartesian4();
        var returnedResultRow0 = Matrix4.getRow(matrix, 0, resultRow0);
        var returnedResultRow1 = Matrix4.getRow(matrix, 1, resultRow1);
        var returnedResultRow2 = Matrix4.getRow(matrix, 2, resultRow2);
        var returnedResultRow3 = Matrix4.getRow(matrix, 3, resultRow3);

        expect(resultRow0).toBe(returnedResultRow0);
        expect(resultRow0).toEqual(expectedRow0);
        expect(resultRow1).toBe(returnedResultRow1);
        expect(resultRow1).toEqual(expectedRow1);
        expect(resultRow2).toBe(returnedResultRow2);
        expect(resultRow2).toEqual(expectedRow2);
        expect(resultRow3).toBe(returnedResultRow3);
        expect(resultRow3).toEqual(expectedRow3);
    });

    it('setRow works for each row', function() {
        var matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var result = new Matrix4();

        var expected = new Matrix4(91.0, 92.0, 93.0, 94.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var returnedResult = Matrix4.setRow(matrix, 0, new Cartesian4(91.0, 92.0, 93.0, 94.0), result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);

        expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 95.0, 96.0, 97.0, 98.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        returnedResult = Matrix4.setRow(matrix, 1, new Cartesian4(95.0, 96.0, 97.0, 98.0), result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);

        expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 99.0, 910.0, 911.0, 912.0, 13.0, 14.0, 15.0, 16.0);
        returnedResult = Matrix4.setRow(matrix, 2, new Cartesian4(99.0, 910.0, 911.0, 912.0), result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);

        expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 913.0, 914.0, 915.0, 916.0);
        returnedResult = Matrix4.setRow(matrix, 3, new Cartesian4(913.0, 914.0, 915.0, 916.0), result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);
    });

    it('getScale works', function() {
        var scale = new Cartesian3(1.0, 2.0, 3.0);
        var result = new Cartesian3();
        var computedScale = Matrix4.getScale(Matrix4.fromScale(scale), result);

        expect(computedScale).toBe(result);
        expect(computedScale).toEqualEpsilon(scale, CesiumMath.EPSILON14);
    });

    it('getScale throws without a matrix', function() {
        expect(function() {
            Matrix4.getScale();
        }).toThrowDeveloperError();
    });

    it('getMaximumScale works', function() {
        var m = Matrix4.fromScale(new Cartesian3(1.0, 2.0, 3.0));
        expect(Matrix4.getMaximumScale(m)).toEqualEpsilon(3.0, CesiumMath.EPSILON14);
    });

    it('getMaximumScale throws without a matrix', function() {
        expect(function() {
            Matrix4.getMaximumScale();
        }).toThrowDeveloperError();
    });

    it('multiply works', function() {
        var left = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var right = new Matrix4(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32);
        var expected = new Matrix4(250, 260, 270, 280, 618, 644, 670, 696, 986, 1028, 1070, 1112, 1354, 1412, 1470, 1528);
        var result = new Matrix4();
        var returnedResult = Matrix4.multiply(left, right, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('multiply works with a result parameter that is an input result parameter', function() {
        var left = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var right = new Matrix4(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32);
        var expected = new Matrix4(250, 260, 270, 280, 618, 644, 670, 696, 986, 1028, 1070, 1112, 1354, 1412, 1470, 1528);
        var returnedResult = Matrix4.multiply(left, right, left);
        expect(returnedResult).toBe(left);
        expect(left).toEqual(expected);
    });

    it('add works', function() {
        var left = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var right = new Matrix4(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32);
        var expected = new Matrix4(18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48);
        var result = new Matrix4();
        var returnedResult = Matrix4.add(left, right, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('add works with a result parameter that is an input result parameter', function() {
        var left = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var right = new Matrix4(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32);
        var expected = new Matrix4(18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48);
        var returnedResult = Matrix4.add(left, right, left);
        expect(returnedResult).toBe(left);
        expect(left).toEqual(expected);
    });

    it('subtract works', function() {
        var left = new Matrix4(18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48);
        var right = new Matrix4(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32);
        var expected = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var result = new Matrix4();
        var returnedResult = Matrix4.subtract(left, right, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('subtract works with a result parameter that is an input result parameter', function() {
        var left = new Matrix4(18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48);
        var right = new Matrix4(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32);
        var expected = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var returnedResult = Matrix4.subtract(left, right, left);
        expect(returnedResult).toBe(left);
        expect(left).toEqual(expected);
    });

    it('multiplyTransformation works', function() {
        var left = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 1);
        var right = new Matrix4(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 0, 0, 0, 1);
        var expected = new Matrix4(134, 140, 146, 156, 386, 404, 422, 448, 638, 668, 698, 740, 0, 0, 0, 1);
        var result = new Matrix4();
        var returnedResult = Matrix4.multiplyTransformation(left, right, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('multiplyTransformation works with a result parameter that is an input result parameter', function() {
        var left = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 1);
        var right = new Matrix4(17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 0, 0, 0, 1);
        var expected = new Matrix4(134, 140, 146, 156, 386, 404, 422, 448, 638, 668, 698, 740, 0, 0, 0, 1);
        var returnedResult = Matrix4.multiplyTransformation(left, right, left);
        expect(returnedResult).toBe(left);
        expect(left).toEqual(expected);
    });

    it('multiplyByMatrix3 works', function() {
        var left = Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(CesiumMath.toRadians(45.0)), new Cartesian3(1.0, 2.0, 3.0));
        var rightRotation = Matrix3.fromRotationX(CesiumMath.toRadians(30.0));
        var right = Matrix4.fromRotationTranslation(rightRotation);
        var expected = new Matrix4.multiplyTransformation(left, right, new Matrix4());
        var result = new Matrix4();
        var returnedResult = Matrix4.multiplyByMatrix3(left, rightRotation, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('multiplyByMatrix3 works with a result parameter that is an input result parameter', function() {
        var left = Matrix4.fromRotationTranslation(Matrix3.fromRotationZ(CesiumMath.toRadians(45.0)), new Cartesian3(1.0, 2.0, 3.0));
        var rightRotation = Matrix3.fromRotationX(CesiumMath.toRadians(30.0));
        var right = Matrix4.fromRotationTranslation(rightRotation);
        var expected = new Matrix4.multiplyTransformation(left, right, new Matrix4());
        var returnedResult = Matrix4.multiplyByMatrix3(left, rightRotation, left);
        expect(returnedResult).toBe(left);
        expect(left).toEqual(expected);
    });

    it('multiplyByTranslation works', function() {
        var m = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 1);
        var translation = new Cartesian3(17, 18, 19);
        var expected = Matrix4.multiply(m, Matrix4.fromTranslation(translation), new Matrix4());
        var result = new Matrix4();
        var returnedResult = Matrix4.multiplyByTranslation(m, translation, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('multiplyByTranslation works with a result parameter that is an input result parameter', function() {
        var m = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 1);
        var translation = new Cartesian3(17, 18, 19);
        var expected = Matrix4.multiply(m, Matrix4.fromTranslation(translation), new Matrix4());
        var returnedResult = Matrix4.multiplyByTranslation(m, translation, m);
        expect(returnedResult).toBe(m);
        expect(m).toEqual(expected);
    });

    it('multiplyByUniformScale works', function() {
        var m = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 1);
        var scale = 1.0;
        var expected = Matrix4.multiply(m, Matrix4.fromUniformScale(scale), new Matrix4());
        var result = new Matrix4();
        var returnedResult = Matrix4.multiplyByUniformScale(m, scale, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('multiplyByUniformScale works with a result parameter that is an input result parameter', function() {
        var m = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 1);
        var scale = 2.0;
        var expected = Matrix4.multiply(m, Matrix4.fromUniformScale(scale), new Matrix4());
        var returnedResult = Matrix4.multiplyByUniformScale(m, scale, m);
        expect(returnedResult).toBe(m);
        expect(m).toEqual(expected);
    });

    it('multiplyByScale works', function() {
        var m = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 1);
        var scale = new Cartesian3(1.0, 1.0, 1.0);
        var expected = Matrix4.multiply(m, Matrix4.fromScale(scale), new Matrix4());
        var result = new Matrix4();
        var returnedResult = Matrix4.multiplyByScale(m, scale, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);

        m = new Matrix4(2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 0, 0, 0, 1);
        scale = new Cartesian3(2.0, 3.0, 4.0);
        expected = Matrix4.multiply(m, Matrix4.fromScale(scale), new Matrix4());
        result = new Matrix4();
        returnedResult = Matrix4.multiplyByScale(m, scale, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('multiplyByScale works with "this" result parameter', function() {
        var m = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0, 1);
        var scale = new Cartesian3(1.0, 2.0, 3.0);
        var expected = Matrix4.multiply(m, Matrix4.fromScale(scale), new Matrix4());
        var returnedResult = Matrix4.multiplyByScale(m, scale, m);
        expect(returnedResult).toBe(m);
        expect(m).toEqual(expected);
    });

    it('multiplyByVector works', function() {
        var left = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var right = new Cartesian4(17, 18, 19, 20);
        var expected = new Cartesian4(190, 486, 782, 1078);
        var result = new Cartesian4();
        var returnedResult = Matrix4.multiplyByVector(left, right, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('multiplyByPoint works', function() {
        var left = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var right = new Cartesian3(17, 18, 19);
        var expected = new Cartesian3(114, 334, 554);
        var result = new Cartesian3();
        var returnedResult = Matrix4.multiplyByPoint(left, right, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('multiplyByPointAsVector works', function() {
        var left = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var right = new Cartesian3(17, 18, 19);
        var expected = new Cartesian3(110, 326, 542);
        var result = new Cartesian3();
        var returnedResult = Matrix4.multiplyByPointAsVector(left, right, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('multiplyByScalar works', function() {
        var left = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var right = 2;
        var expected = new Matrix4(2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32);
        var result = new Matrix4();
        var returnedResult = Matrix4.multiplyByScalar(left, right, result);
        expect(returnedResult).toBe(result);
        expect(result).toEqual(expected);
    });

    it('negate works', function() {
        var matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var expected = new Matrix4(-1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0, -9.0, -10.0, -11.0, -12.0, -13.0, -14.0, -15.0, -16.0);
        var result = new Matrix4();
        var returnedResult = Matrix4.negate(matrix, result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);
    });

    it('negate works with a result parameter that is an input result parameter', function() {
        var matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var expected = new Matrix4(-1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0, -9.0, -10.0, -11.0, -12.0, -13.0, -14.0, -15.0, -16.0);
        var returnedResult = Matrix4.negate(matrix, matrix);
        expect(matrix).toBe(returnedResult);
        expect(matrix).toEqual(expected);
    });

    it('transpose works', function() {
        var matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var expected = new Matrix4(1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0, 4.0, 8.0, 12.0, 16.0);
        var result = new Matrix4();
        var returnedResult = Matrix4.transpose(matrix, result);
        expect(result).toBe(returnedResult);
        expect(result).toEqual(expected);
    });

    it('transpose works with a result parameter that is an input result parameter', function() {
        var matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var expected = new Matrix4(1.0, 5.0, 9.0, 13.0, 2.0, 6.0, 10.0, 14.0, 3.0, 7.0, 11.0, 15.0, 4.0, 8.0, 12.0, 16.0);
        var returnedResult = Matrix4.transpose(matrix, matrix);
        expect(matrix).toBe(returnedResult);
        expect(matrix).toEqual(expected);
    });

    it('equals works in all cases', function() {
        var left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equals(left, right)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(5.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        expect(Matrix4.equals(left, right)).toEqual(false);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 6.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        expect(Matrix4.equals(left, right)).toEqual(false);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 7.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        expect(Matrix4.equals(left, right)).toEqual(false);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 8.0, 5.0, 6.0, 7.0, 8.0, 9.0);
        expect(Matrix4.equals(left, right)).toEqual(false);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 9.0, 6.0, 7.0, 8.0, 9.0);
        expect(Matrix4.equals(left, right)).toEqual(false);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 10.0, 7.0, 8.0, 9.0);
        expect(Matrix4.equals(left, right)).toEqual(false);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 11.0, 8.0, 9.0);
        expect(Matrix4.equals(left, right)).toEqual(false);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 12.0, 9.0);
        expect(Matrix4.equals(left, right)).toEqual(false);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 13.0);
        expect(Matrix4.equals(left, right)).toEqual(false);
    });

    it('equals works with undefined', function() {
        expect(Matrix4.equals(undefined, undefined)).toEqual(true);
        expect(Matrix4.equals(new Matrix4(), undefined)).toEqual(false);
        expect(Matrix4.equals(undefined, new Matrix4())).toEqual(false);
    });

    it('equalsEpsilon works in all cases', function() {
        var left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 1.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(5.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 6.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 7.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 8.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 9.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 10.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 11.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 12.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 13.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 14.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 15.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 16.0, 13.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 17.0, 14.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 18.0, 15.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 19.0, 16.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);

        left = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        right = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 20.0);
        expect(Matrix4.equalsEpsilon(left, right, 3.9)).toEqual(false);
        expect(Matrix4.equalsEpsilon(left, right, 4.0)).toEqual(true);
    });

    it('equalsEpsilon works with undefined', function() {
        expect(Matrix4.equalsEpsilon(undefined, undefined, 1.0)).toEqual(true);
        expect(Matrix4.equalsEpsilon(new Matrix4(), undefined, 1.0)).toEqual(false);
        expect(Matrix4.equalsEpsilon(undefined, new Matrix4(), 1.0)).toEqual(false);
    });

    it('toString', function() {
        var matrix = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        expect(matrix.toString()).toEqual('(1, 2, 3, 4)\n(5, 6, 7, 8)\n(9, 10, 11, 12)\n(13, 14, 15, 16)');
    });

    it('getTranslation works', function() {
        var matrix = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var expected = new Cartesian3(4, 8, 12);
        var result = new Cartesian3();
        var returnedResult = Matrix4.getTranslation(matrix, result);
        expect(returnedResult).toBe(result);
        expect(expected).toEqual(returnedResult);
    });

    it('getMatrix3 works', function() {
        var matrix = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        var expected = new Matrix3(1, 2, 3, 5, 6, 7, 9, 10, 11);
        var result = new Matrix3();
        var returnedResult = Matrix4.getMatrix3(matrix, result);
        expect(returnedResult).toBe(result);
        expect(expected).toEqual(returnedResult);
    });

    it('inverse works', function() {
        var matrix = new Matrix4(0.72,  0.70, 0.00,  0.00,
                                -0.40,  0.41, 0.82,  0.00,
                                 0.57, -0.59, 0.57, -3.86,
                                 0.00,  0.00, 0.00,  1.00);

        var expected = new Matrix4(0.7150830193944467,    -0.3976559229803265,  0.5720664155155574,  2.2081763638900513,
                                   0.6930574657657118,    0.40901752077976433, -0.5884111702445733, -2.271267117144053,
                                   0.0022922521876059163, 0.8210249357172755,   0.5732623731786561,  2.2127927604696125,
                                   0.0,                   0.0,                  0.0,                 1.0);

        var result = new Matrix4();
        var returnedResult = Matrix4.inverse(matrix, result);
        expect(returnedResult).toBe(result);
        expect(expected).toEqualEpsilon(returnedResult, CesiumMath.EPSILON20);
        expect(Matrix4.multiply(returnedResult, matrix, new Matrix4())).toEqualEpsilon(Matrix4.IDENTITY, CesiumMath.EPSILON15);
    });

    it('inverse translates zero scale matrix', function() {
        var matrix = Matrix4.fromTranslation(new Cartesian3(1.0, 2.0, 3.0));
        matrix = Matrix4.multiplyByUniformScale(matrix, 0.0, matrix);
        var expected = Matrix4.fromTranslation(new Cartesian3(-1.0, -2.0, -3.0));
        expected = Matrix4.multiplyByUniformScale(expected, 0.0, expected);

        var result = Matrix4.inverse(matrix, new Matrix4());
        expect(expected).toEqualEpsilon(result, CesiumMath.EPSILON20);
    });

    it('inverse behaves acceptably with near single precision zero scale matrix', function() {
        var trs = new TranslationRotationScale(new Cartesian3(0.0, 0.0, 0.0),
        Quaternion.fromAxisAngle(Cartesian3.UNIT_X, 0.0),
        new Cartesian3(1.0e-7, 1.0e-7, 1.1e-7));

        var matrix = Matrix4.fromTranslationRotationScale(trs);

        var expected = new Matrix4(1e7, 0, 0, 0,
                                   0, 1e7, 0, 0,
                                   0, 0, (1.0/1.1)*1e7, 0,
                                   0, 0, 0, 1);

        var result = Matrix4.inverse(matrix, new Matrix4());
        expect(expected).toEqualEpsilon(result, CesiumMath.EPSILON15);
    });

    it('inverse behaves acceptably with single precision zero scale matrix', function() {
        var trs = new TranslationRotationScale(new Cartesian3(0.0, 0.0, 0.0),
        Quaternion.fromAxisAngle(Cartesian3.UNIT_X, 0.0),
        new Cartesian3(1.8e-8, 1.2e-8, 1.2e-8));

        var matrix = Matrix4.fromTranslationRotationScale(trs);

        var expected = new Matrix4(0, 0, 0, -matrix[12],
                                   0, 0, 0, -matrix[13],
                                   0, 0, 0, -matrix[14],
                                   0, 0, 0, 1);

        var result = Matrix4.inverse(matrix, new Matrix4());
        expect(expected).toEqualEpsilon(result, CesiumMath.EPSILON20);
    });

    it('inverseTransformation works', function() {
        var matrix = new Matrix4(1, 0, 0, 10,
                                 0, 0, 1, 20,
                                 0, 1, 0, 30,
                                 0, 0, 0,  1);

        var expected = new Matrix4(1, 0, 0, -10,
                                   0, 0, 1, -30,
                                   0, 1, 0, -20,
                                   0, 0, 0, 1);

        var result = new Matrix4();
        var returnedResult = Matrix4.inverseTransformation(matrix, result);
        expect(returnedResult).toBe(result);
        expect(expected).toEqual(returnedResult);
        expect(Matrix4.multiply(returnedResult, matrix, new Matrix4())).toEqual(Matrix4.IDENTITY);
    });

    it('abs throws without a matrix', function() {
        expect(function() {
            return Matrix4.abs();
        }).toThrowDeveloperError();
    });

    it('abs works', function() {
        var matrix = new Matrix4(-1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0, -9.0, -10.0, -11.0, -12.0, -13.0, -14.0, -15.0, -16.0);
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var result = new Matrix4();
        var returnedResult = Matrix4.abs(matrix, result);
        expect(returnedResult).toEqual(expected);

        matrix = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        returnedResult = Matrix4.abs(matrix, result);
        expect(returnedResult).toEqual(expected);

        matrix = new Matrix4(1.0, -2.0, -3.0, 4.0, 5.0, -6.0, 7.0, -8.0, 9.0, -10.0, 11.0, -12.0, 13.0, -14.0, 15.0, -16.0);
        returnedResult = Matrix4.abs(matrix, result);
        expect(returnedResult).toEqual(expected);
    });

    it('abs works with a result parameter that is an input result parameter', function() {
        var matrix = new Matrix4(-1.0, -2.0, -3.0, -4.0, -5.0, -6.0, -7.0, -8.0, -9.0, -10.0, -11.0, -12.0, -13.0, -14.0, -15.0, -16.0);
        var expected = new Matrix4(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0);
        var returnedResult = Matrix4.abs(matrix, matrix);
        expect(matrix).toBe(returnedResult);
        expect(matrix).toEqual(expected);
    });

    it('fromArray throws without an array', function() {
        expect(function() {
            return Matrix4.fromArray();
        }).toThrowDeveloperError();
    });

    it('fromRowMajorArray throws with undefined parameter', function() {
        expect(function() {
            Matrix4.fromRowMajorArray(undefined);
        }).toThrowDeveloperError();
    });

    it('fromColumnMajorArray throws with undefined parameter', function() {
        expect(function() {
            Matrix4.fromColumnMajorArray(undefined);
        }).toThrowDeveloperError();
    });

    it('fromRotationTranslation throws without rotation parameter', function() {
        expect(function() {
            Matrix4.fromRotationTranslation(undefined, new Cartesian3());
        }).toThrowDeveloperError();
    });

    it('fromTranslationQuaternionRotationScale throws without translation parameter', function() {
        expect(function() {
            Matrix4.fromTranslationQuaternionRotationScale(undefined, new Quaternion(), new Cartesian3());
        }).toThrowDeveloperError();
    });

    it('fromTranslationQuaternionRotationScale throws without rotation parameter', function() {
        expect(function() {
            Matrix4.fromTranslationQuaternionRotationScale(new Matrix3(), undefined, new Cartesian3());
        }).toThrowDeveloperError();
    });

    it('fromTranslationQuaternionRotationScale throws without scale parameter', function() {
        expect(function() {
            Matrix4.fromTranslationQuaternionRotationScale(new Matrix3(), new Quaternion(), undefined);
        }).toThrowDeveloperError();
    });

    it('fromTranslation throws without translation parameter', function() {
        expect(function() {
            Matrix4.fromTranslation(undefined);
        }).toThrowDeveloperError();
    });

    it('fromScale throws without scale parameter', function() {
        expect(function() {
            Matrix4.fromScale(undefined);
        }).toThrowDeveloperError();
    });

    it('fromUniformScale throws without scale parameter', function() {
        expect(function() {
            Matrix4.fromUniformScale(undefined);
        }).toThrowDeveloperError();
    });

    it('fromCamera throws without camera', function() {
        expect(function() {
            Matrix4.fromCamera(undefined);
        }).toThrowDeveloperError();
    });

    it('fromCamera throws without position', function() {
        expect(function() {
            Matrix4.fromCamera({
                direction : Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
                up : Cartesian3.UNIT_Y
            });
        }).toThrowDeveloperError();
    });

    it('fromCamera throws without direction', function() {
        expect(function() {
            Matrix4.fromCamera({
                position : Cartesian3.ZERO,
                up : Cartesian3.UNIT_Y
            });
        }).toThrowDeveloperError();
    });

    it('fromCamera throws without up', function() {
        expect(function() {
            Matrix4.fromCamera({
                position : Cartesian3.ZERO,
                direction : Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3())
            });
        }).toThrowDeveloperError();
    });

    it('computeOrthographicOffCenter throws without left', function() {
        expect(function() {
            var right = 0, bottom = 0, top = 0, near = 0, far = 0;
            Matrix4.computeOrthographicOffCenter(undefined, right, bottom, top, near, far);
        }).toThrowDeveloperError();
    });

    it('computeOrthographicOffCenter throws without right', function() {
        expect(function() {
            var left = 0, bottom = 0, top = 0, near = 0, far = 0;
            Matrix4.computeOrthographicOffCenter(left, undefined, bottom, top, near, far);
        }).toThrowDeveloperError();
    });

    it('computeOrthographicOffCenter throws without bottom', function() {
        expect(function() {
            var left = 0, right = 0, top = 0, near = 0, far = 0;
            Matrix4.computeOrthographicOffCenter(left, right, undefined, top, near, far);
        }).toThrowDeveloperError();
    });

    it('computeOrthographicOffCenter throws without top', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, near = 0, far = 0;
            Matrix4.computeOrthographicOffCenter(left, right, bottom, undefined, near, far);
        }).toThrowDeveloperError();
    });

    it('computeOrthographicOffCenter throws without near', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, top = 0, far = 0;
            Matrix4.computeOrthographicOffCenter(left, right, bottom, top, undefined, far);
        }).toThrowDeveloperError();
    });

    it('computeOrthographicOffCenter throws without far', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, top = 0, near = 0;
            Matrix4.computeOrthographicOffCenter(left, right, bottom, top, near, undefined);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveOffCenter throws without left', function() {
        expect(function() {
            var right = 0, bottom = 0, top = 0, near = 0, far = 0;
            Matrix4.computePerspectiveOffCenter (undefined, right, bottom, top, near, far);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveOffCenter throws without right', function() {
        expect(function() {
            var left = 0, bottom = 0, top = 0, near = 0, far = 0;
            Matrix4.computePerspectiveOffCenter (left, undefined, bottom, top, near, far);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveOffCenter throws without bottom', function() {
        expect(function() {
            var left = 0, right = 0, top = 0, near = 0, far = 0;
            Matrix4.computePerspectiveOffCenter (left, right, undefined, top, near, far);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveOffCenter throws without top', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, near = 0, far = 0;
            Matrix4.computePerspectiveOffCenter (left, right, bottom, undefined, near, far);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveOffCenter throws without near', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, top = 0, far = 0;
            Matrix4.computePerspectiveOffCenter (left, right, bottom, top, undefined, far);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveOffCenter throws without far', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, top = 0, near = 0;
            Matrix4.computePerspectiveOffCenter (left, right, bottom, top, near, undefined);
        }).toThrowDeveloperError();
    });

    it('computeInfinitePerspectiveOffCenter  throws without left', function() {
        expect(function() {
            var right = 0, bottom = 0, top = 0, near = 0, far = 0;
            Matrix4.computeInfinitePerspectiveOffCenter (undefined, right, bottom, top, near, far);
        }).toThrowDeveloperError();
    });

    it('computeInfinitePerspectiveOffCenter  throws without right', function() {
        expect(function() {
            var left = 0, bottom = 0, top = 0, near = 0, far = 0;
            Matrix4.computeInfinitePerspectiveOffCenter (left, undefined, bottom, top, near, far);
        }).toThrowDeveloperError();
    });

    it('computeInfinitePerspectiveOffCenter  throws without bottom', function() {
        expect(function() {
            var left = 0, right = 0, top = 0, near = 0, far = 0;
            Matrix4.computeInfinitePerspectiveOffCenter (left, right, undefined, top, near, far);
        }).toThrowDeveloperError();
    });

    it('computeInfinitePerspectiveOffCenter  throws without top', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, near = 0, far = 0;
            Matrix4.computeInfinitePerspectiveOffCenter (left, right, bottom, undefined, near, far);
        }).toThrowDeveloperError();
    });

    it('computeInfinitePerspectiveOffCenter  throws without near', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, top = 0, far = 0;
            Matrix4.computeInfinitePerspectiveOffCenter (left, right, bottom, top, undefined, far);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveFieldOfView throws with out of range y field of view', function() {
        expect(function() {
            Matrix4.computePerspectiveFieldOfView(0, 1, 2, 3);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveFieldOfView throws with out of range aspect', function() {
        expect(function() {
            Matrix4.computePerspectiveFieldOfView(1, 0, 2, 3);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveFieldOfView throws with out of range near', function() {
        expect(function() {
            Matrix4.computePerspectiveFieldOfView(1, 1, 0, 3);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveFieldOfView throws with out of range far', function() {
        expect(function() {
            Matrix4.computePerspectiveFieldOfView(1, 1, 2, 0);
        }).toThrowDeveloperError();
    });

    it('clone returns undefined without matrix parameter', function() {
        expect(Matrix4.clone(undefined)).toBeUndefined();
    });

    it('toArray throws without matrix parameter', function() {
        expect(function() {
            Matrix4.toArray(undefined);
        }).toThrowDeveloperError();
    });

    it('getElement throws without row parameter', function() {
        var row;
        var col = 0.0;
        expect(function() {
            Matrix4.getElementIndex(col, row);
        }).toThrowDeveloperError();
    });

    it('getElement throws without column parameter', function() {
        var row = 0.0;
        var col;
        expect(function() {
            Matrix4.getElementIndex(col, row);
        }).toThrowDeveloperError();
    });

    it('getColumn throws without matrix parameter', function() {
        expect(function() {
            Matrix4.getColumn(undefined, 1);
        }).toThrowDeveloperError();
    });

    it('getColumn throws without of range index parameter', function() {
        var matrix = new Matrix4();
        expect(function() {
            Matrix4.getColumn(matrix, 4);
        }).toThrowDeveloperError();
    });

    it('setColumn throws without matrix parameter', function() {
        var cartesian = new Cartesian4();
        expect(function() {
            Matrix4.setColumn(undefined, 2, cartesian);
        }).toThrowDeveloperError();
    });

    it('setColumn throws without cartesian parameter', function() {
        var matrix = new Matrix4();
        expect(function() {
            Matrix4.setColumn(matrix, 1, undefined);
        }).toThrowDeveloperError();
    });

    it('setColumn throws without of range index parameter', function() {
        var matrix = new Matrix4();
        var cartesian = new Cartesian4();
        expect(function() {
            Matrix4.setColumn(matrix, 4, cartesian);
        }).toThrowDeveloperError();
    });

    it('setColumn throws without matrix parameter', function() {
        var cartesian = new Cartesian4();
        expect(function() {
            Matrix4.setColumn(undefined, 2, cartesian);
        }).toThrowDeveloperError();
    });

    it('setTranslation throws without matrix parameter', function() {
        expect(function() {
            Matrix4.setTranslation(undefined, new Cartesian3(), new Matrix4());
        }).toThrowDeveloperError();
    });

    it('setTranslation throws without translation parameter', function() {
        expect(function() {
            Matrix4.setTranslation(new Matrix4(), undefined, new Matrix4());
        }).toThrowDeveloperError();
    });

    it('setTranslation throws without result parameter', function() {
        expect(function() {
            Matrix4.setTranslation(new Matrix4(), new Cartesian3(), undefined);
        }).toThrowDeveloperError();
    });

    it('getRow throws without of range index parameter', function() {
        var matrix = new Matrix4();
        expect(function() {
            Matrix4.getRow(matrix, 4);
        }).toThrowDeveloperError();
    });

    it('setRow throws without matrix parameter', function() {
        var cartesian = new Cartesian4();
        expect(function() {
            Matrix4.setRow(undefined, 2, cartesian);
        }).toThrowDeveloperError();
    });

    it('setRow throws without cartesian parameter', function() {
        var matrix = new Matrix4();
        expect(function() {
            Matrix4.setRow(matrix, 1, undefined);
        }).toThrowDeveloperError();
    });

    it('setRow throws without of range index parameter', function() {
        var matrix = new Matrix4();
        var cartesian = new Cartesian4();
        expect(function() {
            Matrix4.setRow(matrix, 4, cartesian);
        }).toThrowDeveloperError();
    });

    it('multiply throws with no left parameter', function() {
        var right = new Matrix4();
        expect(function() {
            Matrix4.multiply(undefined, right);
        }).toThrowDeveloperError();
    });

    it('multiply throws with no right parameter', function() {
        var left = new Matrix4();
        expect(function() {
            Matrix4.multiply(left, undefined);
        }).toThrowDeveloperError();
    });

    it('multiplyByTranslation throws with no matrix parameter', function() {
        var translation = new Cartesian3();
        expect(function() {
            Matrix4.multiplyByTranslation(undefined, translation);
        }).toThrowDeveloperError();
    });

    it('multiplyByTranslation throws with no translation parameter', function() {
        var m = new Matrix4();
        expect(function() {
            Matrix4.multiplyByTranslation(m, undefined);
        }).toThrowDeveloperError();
    });

    it('multiplyByUniformScale throws with no matrix parameter', function() {
        expect(function() {
            Matrix4.multiplyByUniformScale(undefined, 2.0);
        }).toThrowDeveloperError();
    });

    it('multiplyByUniformScale throws with no scale parameter', function() {
        var m = new Matrix4();
        expect(function() {
            Matrix4.multiplyByUniformScale(m, undefined);
        }).toThrowDeveloperError();
    });

    it('multiplyByScale throws with no matrix parameter', function() {
        expect(function() {
            Matrix4.multiplyByScale(undefined, new Cartesian3());
        }).toThrowDeveloperError();
    });

    it('multiplyByScale throws with no scale parameter', function() {
        var m = new Matrix4();
        expect(function() {
            Matrix4.multiplyByScale(m, undefined);
        }).toThrowDeveloperError();
    });

    it('multiplyByVector throws with no matrix parameter', function() {
        var cartesian = new Cartesian4();
        expect(function() {
            Matrix4.multiplyByVector(undefined, cartesian);
        }).toThrowDeveloperError();
    });

    it('multiplyByVector throws with no cartesian parameter', function() {
        var matrix = new Matrix4();
        expect(function() {
            Matrix4.multiplyByVector(matrix, undefined);
        }).toThrowDeveloperError();
    });

    it('multiplyByPoint throws with no matrix parameter', function() {
        var cartesian = new Cartesian4();
        expect(function() {
            Matrix4.multiplyByPoint(undefined, cartesian);
        }).toThrowDeveloperError();
    });

    it('multiplyByPoint throws with no cartesian parameter', function() {
        var matrix = new Matrix4();
        expect(function() {
            Matrix4.multiplyByPoint(matrix, undefined);
        }).toThrowDeveloperError();
    });

    it('multiplyByScalar throws with no matrix parameter', function() {
        expect(function() {
            Matrix4.multiplyByScalar(undefined, 2);
        }).toThrowDeveloperError();
    });

    it('multiplyByScalar throws with non-numeric scalar parameter', function() {
        var matrix = new Matrix4();
        expect(function() {
            Matrix4.multiplyByScalar(matrix, {});
        }).toThrowDeveloperError();
    });

    it('negate throws without matrix parameter', function() {
        expect(function() {
            Matrix4.negate(undefined);
        }).toThrowDeveloperError();
    });

    it('transpose throws without matrix parameter', function() {
        expect(function() {
            Matrix4.transpose(undefined);
        }).toThrowDeveloperError();
    });

    it('equalsEpsilon throws with non-number parameter', function() {
        expect(function() {
            Matrix4.equalsEpsilon(new Matrix4(), new Matrix4(), {});
        }).toThrowDeveloperError();
    });

    it('getTranslation throws without matrix parameter', function() {
        expect(function() {
            Matrix4.getTranslation(undefined);
        }).toThrowDeveloperError();
    });

    it('getMatrix3 throws without matrix parameter', function() {
        expect(function() {
            Matrix4.getMatrix3(undefined);
        }).toThrowDeveloperError();
    });

    it('inverse throws without matrix parameter', function() {
        expect(function() {
            Matrix4.inverse(undefined);
        }).toThrowDeveloperError();
    });

    it('inverse throws with non-inversable matrix', function() {
        var matrix = new Matrix4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
        expect(function() {
            Matrix4.inverse(matrix, new Matrix4());
        }).toThrowRuntimeError();
    });

    it('inverseTransformation throws without matrix parameter', function() {
        expect(function() {
            Matrix4.inverseTransformation(undefined);
        }).toThrowDeveloperError();
    });

    it('getColumn throws without result parameter', function() {
        expect(function() {
            Matrix4.getColumn(new Matrix4(), 2);
        }).toThrowDeveloperError();
    });

    it('setColumn throws without result parameter', function() {
        expect(function() {
            Matrix4.setColumn(new Matrix4(), 2, new Cartesian4());
        }).toThrowDeveloperError();
    });

    it('getRow throws without result parameter', function() {
        expect(function() {
            Matrix4.getRow(new Matrix4(), 2);
        }).toThrowDeveloperError();
    });

    it('setRow throws without result parameter', function() {
        expect(function() {
            Matrix4.setRow(new Matrix4(), 2, new Cartesian4());
        }).toThrowDeveloperError();
    });

    it('getScale throws without result parameter', function() {
        expect(function() {
            Matrix4.getScale(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('multiply throws without result parameter', function() {
        expect(function() {
            Matrix4.multiply(new Matrix4(), new Matrix3());
        }).toThrowDeveloperError();
    });

    it('multiplyByVector throws without result parameter', function() {
        expect(function() {
            Matrix4.multiplyByVector(new Matrix4(), new Cartesian4());
        }).toThrowDeveloperError();
    });

    it('multiplyByScalar throws without result parameter', function() {
        expect(function() {
            Matrix4.multiplyByScalar(new Matrix4(), 2);
        }).toThrowDeveloperError();
    });

    it('negate throws without result parameter', function() {
        expect(function() {
            Matrix4.negate(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('transpose throws without result parameter', function() {
        expect(function() {
            Matrix4.transpose(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('abs throws without result parameter', function() {
        expect(function() {
            Matrix4.abs(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('inverse throws without result parameter', function() {
        expect(function() {
            Matrix4.inverse(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('multiplyTransformation throws without left parameter', function() {
        expect(function() {
            Matrix4.multiplyTransformation();
        }).toThrowDeveloperError();
    });

    it('multiplyTransformation throws without right parameter', function() {
        expect(function() {
            Matrix4.multiplyTransformation(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('multiplyTransformation throws without result parameter', function() {
        expect(function() {
            Matrix4.multiplyTransformation(new Matrix4(), new Matrix4());
        }).toThrowDeveloperError();
    });

    it('multiplyByMatrix3 throws without left parameter', function() {
        expect(function() {
            Matrix4.multiplyByMatrix3();
        }).toThrowDeveloperError();
    });

    it('multiplyByMatrix3 throws without matrix parameter', function() {
        expect(function() {
            Matrix4.multiplyByMatrix3(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('multiplyByMatrix3 throws without rotation parameter', function() {
        expect(function() {
            Matrix4.multiplyByMatrix3(new Matrix4(), new Matrix3());
        }).toThrowDeveloperError();
    });

    it('multiplyByUniformScale throws without result parameter', function() {
        expect(function() {
            Matrix4.multiplyByUniformScale(new Matrix4(), 2);
        }).toThrowDeveloperError();
    });

    it('multiplyByScale throws without result parameter', function() {
        expect(function() {
            Matrix4.multiplyByScale(new Matrix4(), new Cartesian3());
        }).toThrowDeveloperError();
    });

    it('multiplyByPointAsVector throws without matrix parameter', function() {
        expect(function() {
            Matrix4.multiplyByPointAsVector();
        }).toThrowDeveloperError();
    });

    it('multiplyByPointAsVector throws without cartesian parameter', function() {
        expect(function() {
            Matrix4.multiplyByPointAsVector(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('multiplyByPointAsVector throws without result parameter', function() {
        expect(function() {
            Matrix4.multiplyByPointAsVector(new Matrix4(), new Cartesian3());
        }).toThrowDeveloperError();
    });

    it('multiplyByPoint throws without matrix parameter', function() {
        expect(function() {
            Matrix4.multiplyByPoint(new Matrix4(), new Cartesian3());
        }).toThrowDeveloperError();
    });

    it('getTranslation throws without result parameter', function() {
        expect(function() {
            Matrix4.getTranslation(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('getMatrix3 throws without result parameter', function() {
        expect(function() {
            Matrix4.getMatrix3(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('inverseTransformtation throws without result parameter', function() {
        expect(function() {
            Matrix4.inverseTransformation(new Matrix4());
        }).toThrowDeveloperError();
    });

    it('multiplyByTranslation throws without result parameter', function() {
        expect(function() {
            Matrix4.multiplyByTranslation(new Matrix4(), new Cartesian3());
        }).toThrowDeveloperError();
    });

    it('computePerspectiveFieldOfView throws without result parameter', function() {
        expect(function() {
            Matrix4.computePerspectiveFieldOfView(CesiumMath.PI_OVER_TWO, 1, 1, 10);
        }).toThrowDeveloperError();
    });

    it('computeOrthographicOffCenter throws without result parameter', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, top = 0, near = 0;
            Matrix4.computeOrthographicOffCenter(left, right, bottom, top, near, 0);
        }).toThrowDeveloperError();
    });

    it('computePerspectiveOffCenter throws without result parameter', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, top = 0, near = 0;
            Matrix4.computePerspectiveOffCenter (left, right, bottom, top, near, 0);
        }).toThrowDeveloperError();
    });

    it('computeInfinitePerspectiveOffCenter throws without near', function() {
        expect(function() {
            var left = 0, right = 0, bottom = 0, top = 0;
            Matrix4.computeInfinitePerspectiveOffCenter (left, right, bottom, top, 0);
        }).toThrowDeveloperError();
    });

    it('computeViewportTransformation works', function() {
        expect(function() {
            Matrix4.computeViewportTransformation ({
                x : 0,
                y : 0,
                width : 4.0,
                height : 6.0
            }, 0.0, 2.0);
        }).toThrowDeveloperError();
    });

    it('Matrix4 objects can be used as array like objects', function() {
        var matrix = new Matrix4(
                1, 5, 9, 13,
                2, 6, 10, 14,
                3, 7, 11, 15,
                4, 8, 12, 16);
        expect(matrix.length).toEqual(16);
        var intArray = new Uint32Array(matrix.length);
        intArray.set(matrix);
        for ( var index = 0; index < matrix.length; index++) {
            expect(intArray[index]).toEqual(index + 1);
        }
    });
});
